#include "common.h"
#include "server.h"

SSLServer::SSLServer(const char* keyfile, const char* passw, int port,
		void(*handler)(SSL* ssl, int sock)) :
	handler(handler), port(port)

{
	/* Build our SSL context*/
	ctx = initialize_ctx(keyfile, passw);
	load_dh_params(ctx, DHFILE);
}

SSLServer::~SSLServer() {
	destroy_ctx(ctx);
}

void SSLServer::WaitForConnection() {
	int sock, s;
	BIO *sbio;
	SSL *ssl;
	int r;
	pid_t pid;
	struct sigaction sig_act;

	memset(&sig_act, 0, sizeof(sig_act));
	sig_act.sa_handler = SIG_IGN;

	if (sigaction(SIGCHLD, &sig_act, 0)) {
		perror("sigaction");
		exit(1);
	}

	sock = tcp_listen(port);
	struct linger ling;
	ling.l_onoff = 0;
	ling.l_linger = 0;
	setsockopt(sock, SOL_SOCKET, SO_LINGER, &ling, sizeof(struct linger));

	printf("Listening on port: %d\n", port);

	int i = 1;
	while (1) {
		printf("i: %d\n", i);
		if (i++ > TRANSACTIONS_PER_EXPERIMENT) {
		  while ((pid = wait(NULL)) != -1);
			return;
		}
		if ((s = accept(sock, 0, 0)) < 0)
			err_exit("Problem accepting");

		if ((pid = fork())) {
			close(s);
		} else {
			sbio = BIO_new_socket(s, BIO_NOCLOSE);
			ssl = SSL_new(ctx);
			SSL_set_bio(ssl, sbio, sbio);

			if ((r = SSL_accept(ssl) <= 0))
				berr_exit("SSL accept error");

			handler(ssl, s);

			r = SSL_shutdown(ssl);
			if (!r) {
				/* If we called SSL_shutdown() first then
				 we always get return value of '0'. In
				 this case, try again, but first send a
				 TCP FIN to trigger the other side's
				 close_notify*/
				shutdown(s, 1);
				r = SSL_shutdown(ssl);
			}

			switch (r) {
			case 1:
				break; /* Success */
			case 0:
			case -1:
			default:
				;
				berr_exit("Shutdown failed");
			}

			SSL_free(ssl);
			close(s);

			exit(0);
		}
	}
}

int tcp_listen(int port) {
	int sock;
	struct sockaddr_in sin;
	int val = 1;

	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		err_exit("Couldn't make socket");

	memset(&sin, 0, sizeof(sin));
	sin.sin_addr.s_addr = INADDR_ANY;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(port);
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));

	if (bind(sock, (struct sockaddr *) &sin, sizeof(sin)) < 0)
		berr_exit("Couldn't bind");
	listen(sock, 5);

	return (sock);
}

void load_dh_params(SSL_CTX *ctx, const char *file) {
	DH *ret = 0;
	BIO *bio;

	if ((bio = BIO_new_file(file, "r")) == NULL)
		berr_exit("Couldn't open DH file");

	ret = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
	BIO_free(bio);
	if (SSL_CTX_set_tmp_dh(ctx,ret) < 0)
		berr_exit("Couldn't set DH parameters");
	DH_free(ret);
}

void generate_eph_rsa_key(SSL_CTX *ctx) {
	RSA *rsa;

	rsa = RSA_generate_key(512, RSA_F4, NULL, NULL);

	if (!SSL_CTX_set_tmp_rsa(ctx,rsa))
		berr_exit("Couldn't set RSA key");

	RSA_free(rsa);
}

